/*
 * MergeNodeAction.java
 *
 * Created on January 8, 2007, 12:25 AM
 *
 * Copyright 2008 David D. Emory
 * 
 * This file is part of Five Points. See <http://www.fpdev.org> for
 * additional information regarding the project.
 * 
 * Five Points is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Five Points is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Five Points.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fpdev.apps.rtemaster.actions;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.fpdev.core.basenet.BaseNetwork;
import org.fpdev.core.basenet.BLink;
import org.fpdev.core.basenet.BNode;
import org.fpdev.core.basenet.Path;
import org.fpdev.core.data.SpatialDataConnection.LinkAttr;
import org.fpdev.core.data.SpatialDataConnection.NodeAttr;
import org.fpdev.core.transit.TransitPath;
import org.fpdev.apps.rtemaster.RouteMaster;

/**
 *
 * @author demory
 */
public class MergeNodeAction implements ACAction {

  //private CNode delNode_, mergeNode_;
  private int node1ID_,  node2ID_;
  private BNode node1_,  node2_;
  private DeleteNodeAction delNodeAction_;
  private Set<DeleteLinkAction> delLinks_;
  private Map<BLink, LinkModInfo> modLinks_;
  private Set<TransitPath> modTPaths_;

  /** Creates a new instance of MergeNodeAction */
  public MergeNodeAction(int node1ID, int node2ID) {
    node1ID_ = node1ID;
    node2ID_ = node2ID;
  }

  public String getName() {
    return "Merge Node";
  }

  public boolean doAction(RouteMaster ac) {

    node1_ = ac.getEngine().getBaseNet().getNodeFromID(node1ID_);
    node2_ = ac.getEngine().getBaseNet().getNodeFromID(node2ID_);

    delLinks_ = new HashSet<DeleteLinkAction>();
    modLinks_ = new HashMap<BLink, LinkModInfo>();

    Set<BLink> cLinkSet = ac.getEngine().getBaseNet().connectingLinks(node1_, node2_);

    // delete any links that connected the two nodes 
    int cEdgeCount = 0, cEdgeLen = 0;
    Iterator<BLink> cLinks = cLinkSet.iterator();
    while (cLinks.hasNext()) {
      BLink link = cLinks.next();
      DeleteLinkAction action = new DeleteLinkAction(link, true);
      action.doAction(ac);
      delLinks_.add(action);
      cEdgeCount++;
      cEdgeLen = link.getLengthFeet();
    }
    if (cEdgeCount != 1) {
      cEdgeLen = 0;
    } // don't adjust length if >1 conn edges


    // iterate through incident links and update endpoints
    ac.msg("ilink count = " + ac.getEngine().getBaseNet().incidentLinks(node1_).size());
    Iterator<BLink> iLinks = new HashSet<BLink>(ac.getEngine().getBaseNet().incidentLinks(node1_)).iterator();
    while (iLinks.hasNext()) {
      BLink link = iLinks.next();
      modLinks_.put(link, new LinkModInfo(link.getFNodeID(), link.getTNodeID(), link.getLengthFeet()));
      link.setLengthFeet(link.getLengthFeet() + cEdgeLen);
      ac.getEngine().getBaseNet().deleteLink(link);

      if (link.getFNodeID() == node1ID_) {
        ac.msg("setting as fnode");
        link.setFNode(node2_);
      } else {
        ac.msg("setting as tnode");
        link.setTNode(node2_);
      }
      ac.getEngine().getBaseNet().addLink(link);
    }

    // delete the node being merged
    delNodeAction_ = new DeleteNodeAction(node1_, true);
    delNodeAction_.doAction(ac);

    // update affected transit paths
    modTPaths_ = new HashSet<TransitPath>();
    cLinks = cLinkSet.iterator();
    ac.msg("running merge loop");
    while (cLinks.hasNext()) {
      BLink link = cLinks.next();
      ac.msg("in merge loop for link " + link.getID());
      for(Path path : link.getRegisteredPaths()) {
        if (path.isTransitPath()) {
          TransitPath tPath = (TransitPath) path;
          ac.msg("merging node for path " + tPath.getSubRoute().getMasterID());
          ac.msg("before len=" + tPath.linkCount());
          modTPaths_.add(tPath.createCopy(ac.getEngine(), tPath.getSubRoute(), false));
          tPath.mergeNode(node1_, link, node2_);
          ac.msg("after len=" + tPath.linkCount());
        }
      }
    }
    if(!modTPaths_.isEmpty()) ac.getEngine().getRoutes().changesMade();

    // update affected station
    if (node1_.isStation()) {
      String nodeName = node1_.getStaNodeName();
      node1_.getStation().addNode(node2_);
      node1_.getStation().setNodeName(node2_, nodeName);
      node1_.getStation().removeNode(node1_);
      ac.getStationOps().writeStationsFile();
    }

    // update database (deleted node & links already handled)
    ac.getEngine().getDataPackage().getSpatialDataConn().modifyNode(node2_, new NodeAttr[] { NodeAttr.SHAPE });
    ac.getEngine().getDataPackage().getSpatialDataConn().modifyLinks(modLinks_.keySet(), new LinkAttr[] { LinkAttr.SHAPE, LinkAttr.LEN });

    return true;
  }

  public boolean undoAction(RouteMaster ac) {
    BaseNetwork net = ac.getEngine().getBaseNet();

    // restore deleted node
    delNodeAction_.undoAction(ac);

    // restore deleted links
    for (Iterator<DeleteLinkAction> actions = delLinks_.iterator(); actions.hasNext();) {
      actions.next().undoAction(ac);
    }

    // restore modified links
    for (Iterator<Map.Entry<BLink, LinkModInfo>> entries = modLinks_.entrySet().iterator(); entries.hasNext();) {
      Map.Entry<BLink, LinkModInfo> entry = entries.next();
      BLink link = entry.getKey();
      net.deleteLink(link);
      link.setFNode(ac.getEngine().getNodeFromID(entry.getValue().oldFNodeID));
      link.setTNode(ac.getEngine().getNodeFromID(entry.getValue().oldTNodeID));
      link.setLengthFeet(entry.getValue().oldLen);
      link.updateXY();
      link.updateBBXY();
      net.addLink(link);

    }
    //db.updateLinks(modLinks_.keySet().iterator());
    ac.getEngine().getDataPackage().getSpatialDataConn().modifyLinks(modLinks_.keySet(), new LinkAttr[] { LinkAttr.SHAPE, LinkAttr.LEN });

    // restore  paths
    for (Iterator<TransitPath> tpaths = modTPaths_.iterator(); tpaths.hasNext();) {
      TransitPath tpath = tpaths.next();
      ac.msg("Restoring path " + tpath.getSubRoute().getMasterID() + " len=" + tpath.linkCount());
      tpath.getSubRoute().setPath(tpath);
      ac.getGUI().getMapPanel().getDrawItems().transitPathItemUpdated(tpath);
    }

    // update merged node
    ac.getEngine().getDataPackage().getSpatialDataConn().modifyNode(node2_, new NodeAttr[] { NodeAttr.SHAPE });

    return true;
  }

  private class LinkModInfo {

    int oldFNodeID, oldTNodeID, oldLen;

    public LinkModInfo(int fNodeID, int tNodeID, int len) {
      oldFNodeID = fNodeID;
      oldTNodeID = tNodeID;
      oldLen = len;
    }
  }
}
